package peerToPeerFacebook.proj;

import java.io.Serializable;
import java.util.Arrays;

/** This class is used inside Paxos state machine to define each step. Basically this is input to the state machine.
 */
public class PaxosCommand implements Serializable
{
    private static final long serialVersionUID = 1L;

    /** Method name to invoke */
    protected String method = null;
    
    /** Parameters that should be passed to the target method */
    protected Serializable[] params = new Serializable[] {};
    
    /** Sequance ID of the call that client is making. This is used to make sure that calls are unique */
    protected int sequenceID = 0;
    
    /** Client that is making a call */
    protected int clientID = 0;
    
    /** Construcst paxos command.
     * 
     * @param method Method to invoke
     * @param params Parameters to pass
     */
    public PaxosCommand (int clientID, int sequenceID, String method, Serializable[] params)
    {
        assert method != null: "Method cannot be null";
        assert params != null: "Params cannot be null. Should it be empty array?";
        
        this.method = method;
        this.params = params;
        this.sequenceID = sequenceID;
        this.clientID = clientID;
    }
    
    /** In general method cannot be null. This private constructor works around that by allowing local class to
     * initialize method and params to null. This definition signals NOP command. 
     */
    protected PaxosCommand()
    {
        method = null;
        params = null;
    }
    
    /** Returns command that represents No Operations.
     */
    public static PaxosCommand nop()
    {
        return new PaxosCommand();
    }
    
    /** Checks if the command received is No Operation.
     * return True if this is NOP
     */
    public boolean isNOP()
    {
        return method == null;
    }
    
    @Override
    public boolean equals(Object obj)
    {
        if (!(obj instanceof PaxosCommand))
            return false;
        
        PaxosCommand other = (PaxosCommand)obj;
        return method == null ? other.method == null : method.equals(other.method) &&  
                Arrays.equals(params, other.params) && this.clientID == other.clientID &&
                this.sequenceID == other.sequenceID;
    }
    
    @Override
    public int hashCode()
    {
        // Construct temp array and calculate has code of it
        Serializable[] tmpArray = Arrays.copyOf(params, params.length + 3);
        tmpArray[params.length] = method;
        tmpArray[params.length + 1] = clientID;
        tmpArray[params.length + 2] = sequenceID;
        return Arrays.hashCode(tmpArray);
    }
    
    @Override
    public String toString()
    {
        if( method == null && params == null)  // empty method and NULL params
            return "(NULL)";
        else
            return method + "(" + getStringDefinition(params) + ") - " + clientID + ":" + sequenceID;
    }
    
    /** Returns nice definition of the object array, that can be read.
     * 
     * @param objects Objects to convert to string definition.
     * @return String definition.
     */
    protected StringBuilder getStringDefinition(Serializable[] objects)
    {
        StringBuilder paramNames = new StringBuilder();
        for (int i = 0; i < objects.length; i++)
        {
            if (objects[i] != null)
            {
                paramNames.append(objects[i].getClass().getName());
                paramNames.append("(");
                paramNames.append(objects[i].toString());
                paramNames.append(")");
            }
            else
                paramNames.append("null");
            
            if (i + 1 < objects.length)
                paramNames.append(", ");
        }
        return paramNames;
    }
}
